package badugaserver;
import java.util.Random;

/**
 * @author Wojtek N., Kamil M.
 * @version 0.7
 * Table is might be initiated by Listener.class or Server.class (if something fails)
 * Control advanced aspects of the game mechanic.
 */
public class Table extends Thread
{
	private int allPlayers;
	private int smallBlind;
	private int bigBlind;
	private int gameNumber;
	private int gamePhase = 1;
	private int dealer;
	private int[] maximalBet = new int[4];
	
	private Deck deck;
	private WorkerRunnable[] sharedWorker;
	private Hand[] playersHands;
	private Random rng;
	private boolean start;
	
	/**
	 * Constructor of the class Table.
	 */
	public Table(int allPlayers, int smallBlind, int bigBlind)
	{
		this.allPlayers = allPlayers;
		this.smallBlind = smallBlind;
		this.bigBlind = bigBlind;
		deck = new Deck();
		rng = new Random();
		gameNumber = 1;
		start = true;
		for(int i=0; i<maximalBet.length; i++)
		{
			maximalBet[i] = 0;
		}
	}
	
	public void setSharedWorker(WorkerRunnable[] worker)
	{
		this.sharedWorker = worker;
	}
	
	/**
	 * Prints string on server console.
	 */
	private void serverPrint(String s)
	{
		System.out.println(s);
	}
	
	/**
	 * Method checks if game can continue to the next round.
	 */
	private boolean nextRonud()
	{
		serverPrint("Checking if players want to continue...");
		for(int i=0; i<allPlayers; i++)
		{
			sharedWorker[i].setPlayerState("wait");
		}
		informAll(+allPlayers+"NXT__YN");
		do { }
		while (!areThreadsReady() && areAllAlive());
		serverPrint("Checking answers...");
		for(int i=0; i<allPlayers; i++)
		{
			if("N".equals(sharedWorker[i].getData()) || !sharedWorker[i].isAlive())
			{
				sharedWorker[i].playerQuit();
				informAll(allPlayers + "NXT__ED");
				return false;
			}
			if("Y".equals(sharedWorker[i].getData()) && sharedWorker[i].getStartingTokens() <=0)
			{
				informAll(allPlayers + "NXT__PR"+i);
				serverPrint("Player " + i + " wants to continue, but he is broken!");
				return false;
			}
		}
		return true;
	}
	
	/**
	 * Method in which table checks, are players ready to start.
	 */
	private void ready()
	{
		synchronized(this)
		{
			serverPrint("Checking if players are ready to start...");
			for(int i=0; i<allPlayers; i++)
			{
				sharedWorker[i].setPlayerState("wait");
			}
			informAll(allPlayers+"RDY__?");
			do { }
			while (!areThreadsReady() && areAllAlive());
			serverPrint("Players are ready to start.");
			for(int i=0; i<allPlayers; i++)
			{
				sharedWorker[i].setPlayerState("nrdy");
			}
		}
	}
	
	/**
	 * Method which send information to all clients.
	 * @param s string to be sent
	 */
	private void informAll(String s)
	{
		synchronized(this)
		{
			for(int i=0; i<sharedWorker.length; i++)
			{
				if(sharedWorker[i] != null)
				{
					sharedWorker[i].sendToClient(s);
				}
			}	
		}
	}
	
	/**
	 * Method which send information to one specified client.
	 * @param i client's ID
	 * @param s string to be sent
	 */
	private void sendToClient(int i, String s)
	{
		synchronized(this)
		{
			if(sharedWorker[i] != null)
			{
				sharedWorker[i].sendToClient(s);
			}
		}
	}
	
	/**
	 * Method which checks if clients are ready.
	 */
	private boolean areThreadsReady()
	{
		synchronized(this)
		{
			for(int i=0; i<sharedWorker.length; i++)
			{
				if((sharedWorker[i].playing()) && !"rdy".equals(sharedWorker[i].checkState()))
				{
					return false;
				}
			}
			return true;
		}
	}
	
	/**
	 * Method which checks are all threads (connections) alive.
	 */
	private boolean areAllAlive()
	{
		for(int i=0; i<allPlayers; i++)
		{
			if(!sharedWorker[i].isAlive())
			{
				return false;
			}
		}
		return true;
	}
	
	/**
	 * Check do every player have allowed opening statistics.
	 */
	private boolean doesEverythingOK()
	{
		for(int i=0; i<allPlayers; i++)
		{
			if(sharedWorker[i] == null || sharedWorker[i].getStartingTokens()<0 || !sharedWorker[i].isAlive())
			{
				return false;
			}
		}
		return true;
	}
	
	/**
	 * Method in which bet of the player is set in a round.
	 * @param playerID player for which bet is set
	 * @param howMany tokens amount which is set in this round
	 * @param round round in which bet is set
	 */
	private void bet(int playerID, int howMany, int round) throws BadugaExceptions
	{
		synchronized(this)
		{
			sharedWorker[playerID].setBetRound(howMany, round);
			if(sharedWorker[playerID].getBetRound(round)>maximalBet[round/2-1])
			{
				maximalBet[round/2-1] = sharedWorker[playerID].getBetRound(round);
			}
		}
	}
	
	/**
	 * Method for swaping card in a player hand.
	 * @param playerID ID of player whose card gonna be exchanged
	 * @param which index of card which gonna be exchanged in a hand
	 */
	private void swapCard(int playerID, int which)
	{
		
		Card oldC = playersHands[playerID].removeCard(which);
		Card newC = deck.swapCard(oldC);
		playersHands[playerID].setCard(which, newC);
		
		//playersHands[playerID].setCard(which, deck.swapCard(playersHands[playerID].removeCard(which)));
	}
	
	/**
	 * Method to exchange cards from player hand.
	 */
	private void swaper(String s, int id)
	{
		//serverPrint("***************************DEBUG*****************************\nPlayer " + id +" His current cards:\n"+ playersHands[id].toString(true));
		if(s.equals("FFFF"))
		{
			serverPrint("Player " + id + " decided not to exchange any cards.");
			informAll(id+"CHG__0");
		}
		else
		{
			int count = 0;
			for(int i=0; i<4; i++)
			{
				if(s.substring(i, i+1).equals("T"))
				{
					swapCard(id, i);
					count++;
				}
			}
			//serverPrint("Player " + id +" His new cards:\n"+ playersHands[id].toString(true) + "\n***************************END DEBUG*****************************\n");
			sendToClient(id, allPlayers+"CHGcd"+id+playersHands[id].toString(false));
			informAll(id+"CHG__"+count);
		}
	}
	
	/**
	 * Method for drawing cards for players phase.
	 */
	private int drawRound(int round)
	{
		String move;
		round++;
		informAll(allPlayers+"CHG___");
		serverPrint("Changing cards phase.");
		for(int id=0; id<allPlayers; id++)
		{
			if(sharedWorker[id].isAllIn() || sharedWorker[id].playing())
			{
				sharedWorker[id].setPlayerState("wait");
				sendToClient(id, allPlayers+"CHGos"+id);
				serverPrint("Waiting for answer from player " + id);
				do { }
				while("wait".equals(sharedWorker[id].checkState()) && sharedWorker[id].isAlive());
				serverPrint("Got answet from player " + id);
				move = sharedWorker[id].getMove();
				swaper(move, id);
			}
		}
		return round;
	}
	
	/**
	 * Method which sets possible actions for players.
	 */
	private void setAction(String s)
	{
		for(int x=0; x<allPlayers; x++)
		{
			sharedWorker[x].setAvailableActions(s);
		}
	}
	
	/**
	 * Method for betting phase.
	 */
	private int betRound(int round) throws BadugaExceptions
	{
		serverPrint("Betting round is about to start.");
		int smallBlindPlayer = (dealer+1)%allPlayers;
		int bigBlindPlayer = (dealer+2)%allPlayers;
		int beginer = 0;
		int actual;
		String move;
		setAction("albecachfora");
		
		if(round == 2)
		{
			bet(smallBlindPlayer, smallBlind, round);
			bet(bigBlindPlayer, bigBlind, round);
			for(int x=0; x<allPlayers; x++)
			{
				if(x != bigBlindPlayer)
				{
					sharedWorker[x].setAvailableActions("albecafo");
				}
				else if(x == bigBlindPlayer)
				{
					sharedWorker[x].setAvailableActions("albechfo");
				}
			}
			beginer = (bigBlindPlayer+1)%allPlayers;
		}
		else
		{
			beginer = smallBlindPlayer;
		}
		informAll(allPlayers+"AUC__"+round);
		if(round == 2)
		{
			serverPrint("Player " + dealer + " has dealer button, \nPlayer " + smallBlindPlayer + " paid smallBlind, \nPlayer " + bigBlindPlayer + " paid bigBlind.");
		}
		serverPrint("Player " + beginer + " starts betting round");
		do
		{
			actual = beginer;
			do
			{
				if(sharedWorker[actual].playing())
				{
					if(!"rdy".equals(sharedWorker[actual].checkState()))
					{
						sharedWorker[actual].setPlayerState("wait");
						sendToClient(actual, allPlayers+"AUCur"+round);
						serverPrint("Asking player " +actual+ " for his move.");
						do { }
						while("wait".equals(sharedWorker[actual].checkState()) && sharedWorker[actual].isAlive());
						move = sharedWorker[actual].getMove();
						if(move.equals("fo"))
						{
							sharedWorker[actual].setPlayerState(false);
							sharedWorker[actual].setPlayerState("rdy");
							informAll(allPlayers+"AUCfo"+round+actual);
							serverPrint("Player "+actual+" decided to fold.");
						}
						else if(move.equals("be"))
						{
							sharedWorker[actual].setPlayerState("rdy");
							bet(actual, Integer.parseInt(sharedWorker[actual].getData()), round);
							setAction("alcafora");
							informAll(allPlayers+"AUCbe"+round+actual+sharedWorker[actual].getStartingTokens()+"|"+sharedWorker[actual].getData());
							serverPrint("Player "+actual+" decided to bet for "+sharedWorker[actual].getData()+".");
						}
						else if(move.equals("ca"))
						{
							sharedWorker[actual].setPlayerState("rdy");
							bet(actual, Integer.parseInt(sharedWorker[actual].getData()), round);
							setAction("albecachfora");
							informAll(allPlayers+"AUCca"+round+actual+sharedWorker[actual].getStartingTokens()+"|"+sharedWorker[actual].getData());
							serverPrint("Player "+actual+" decided to call adding to the poll "+sharedWorker[actual].getData()+".");
						}
						else if(move.equals("ch"))
						{
							sharedWorker[actual].setPlayerState("rdy");
							informAll(allPlayers+"AUCch"+round+actual);
							serverPrint("Player "+actual+" decided to check.");
						}
						else if(move.equals("ra"))
						{
							sharedWorker[actual].setPlayerState("rdy");
							bet(actual, Integer.parseInt(sharedWorker[actual].getData()), round);
							setAction("alcafora");
							informAll(allPlayers+"AUCra"+round+actual+sharedWorker[actual].getStartingTokens()+"|"+sharedWorker[actual].getData());
							serverPrint("Player "+actual+" decided to raise for "+sharedWorker[actual].getData()+".");
						}
						else if(move.equals("al"))
						{
							sharedWorker[actual].setPlayerState(false);
							sharedWorker[actual].setPlayerState("rdy");
							sharedWorker[actual].setAllIn(true, round);
							bet(actual, sharedWorker[actual].getStartingTokens(), round);
							informAll(allPlayers+"AUCal"+round+actual+sharedWorker[actual].getData());
							serverPrint("Player "+actual+" decided to all-in adding to the pol "+sharedWorker[actual].getData()+".");
						}
						else
						{
							throw new BadugaExceptions(3);
						}
						
						if(move.equals("be") || move.equals("ra"))
						{
							synchronized(this)
							{
								for(int i=0; i<sharedWorker.length; i++)
								{
									if(sharedWorker[i] != null && sharedWorker[i].playing() && sharedWorker[i] != sharedWorker[actual])
									{
										sharedWorker[i].setPlayerState("nrdy");
									}
								}
							}
						}
					}
				}
			actual = (actual+1)%allPlayers;
			}
			while (actual != beginer);
		}
		while(!areThreadsReady());
		
		int playersLeft = 0;
		for(int i=0; i<sharedWorker.length; i++)
		{
			if(sharedWorker[i] != null && sharedWorker[i].playing())
			{
				playersLeft++;
			}
		}
		if(playersLeft>1)
		{
			if(round == 2)
			{
				return 3;
			}
			else if(round == 4)
			{
				return 5;
			}
			else if(round == 6)
			{
				return 7;
			}
			else if(round == 8)
			{
				return 9;
			}
		}

		return 9;
			
	}
	
	/**
	 * Method which switch between phases.
	 */
	private int phaser(int startinground)
	{
		if((startinground%2) == 0)
		{
			try
			{
				return betRound(startinground);
			}
			catch(BadugaExceptions be)
			{
				be.getMessage();
			}
		}
		else
		{
			return drawRound(startinground);
		}
		return -1;
	}
	
	/**
	 * Method for phase 9, in which winner(s) is(are) selected.
	 */
	private int winnerFinder()
	{
		serverPrint("RNG start working. Winner will be known soon...");
	
		int playersLeft = 0;
		int winner = 0;
		for(int i=0; i<allPlayers; i++)
		{
			if(sharedWorker[i].playing() || sharedWorker[i].isAllIn())
			{
				playersLeft++;
			}
		}
		serverPrint("Number of players left in game: "+playersLeft);
		
		if(playersLeft == 1)
		{
			for(int i=0; i<allPlayers; i++)
			{
				if(sharedWorker[i].playing() || sharedWorker[i].isAllIn())
				{
					return winner = i;
				}
			}
		}
		else
		{
			Hand[] leftHands = new Hand[playersLeft];
			int k=0;
			for(int i=0; i<allPlayers; i++)
			{
				if(sharedWorker[i].playing() || sharedWorker[i].isAllIn())
				{
					leftHands[k] = playersHands[i];
					k++;
				}
			}
			winner = Ranking.lookForTheWinner(leftHands);
		}
		int bank = 0;
		if (winner==0 || winner==1 || winner==2 || winner==3 || winner==4 || winner==5)
		{
			for(int i=0; i<allPlayers; i++)
			{
				bank = bank + sharedWorker[i].allBettedMoney();
			}
			if(sharedWorker[winner].isAllIn())
			{
				int winnerTokens = sharedWorker[winner].allBettedMoney();
				int tokensForWinner = 0;
				int temp = 0;
				for(int i=0; i<allPlayers; i++)
				{
					temp = sharedWorker[i].allBettedMoney() - winnerTokens;
					if(temp<0)
					{
						tokensForWinner = tokensForWinner + sharedWorker[i].allBettedMoney();
					}
					else
					{
						tokensForWinner = tokensForWinner + sharedWorker[i].allBettedMoney();
						sharedWorker[i].takePrize(temp);
						sendToClient(i, i+"WON__"+temp);
					}
				}
				sharedWorker[winner].takePrize(tokensForWinner);
				informAll(allPlayers+"WONon"+winner+"|"+bank);
				serverPrint("Player "+winner+" have won by all-in "+temp+".");
			}
			else
			{
				sharedWorker[winner].takePrize(bank);
				informAll(allPlayers+"WONon"+winner+"|"+bank);
				serverPrint("Player "+winner+" have won "+bank+".");
			}
		}
		else
		{
			String temp = Integer.toString(winner);
			int[] drawers = new int[temp.length()];
			for(int i=0; i<temp.length(); i++)
			{
				try
				{
					drawers[i] = Integer.parseInt(temp.substring(i, i+1));
				}
				catch(NumberFormatException nfe) {	}
			}
			for(int i=0; i<allPlayers; i++)
			{
				bank = bank + sharedWorker[i].allBettedMoney();
			}
			for(int i=0; i<drawers.length; i++)
			{
				sharedWorker[drawers[i]].takePrize(bank/drawers.length);
			}
			informAll(allPlayers+"WONdr"+winner+"|"+bank/drawers.length);
			serverPrint("Players "+temp+" have won "+bank/drawers.length+".");
		}
		return winner;
	}
	
	@Override
	public void run()
	{
		serverPrint("Table was successfully created. Checking if everything is all right.");
		ready();
		if(!areAllAlive())
		{
			serverPrint("Missing few players (possibly disconnected).");
			informAll("Missing few players.");
			start = false;
		}
		else
		{
			serverPrint("Ready to go, Sir.");
		}
		while(start)
		{
			if(!doesEverythingOK())
			{
				informAll("Player logged out or does not have enough tokens.");
				gamePhase = -1;
				start = false;
				break;
			}
			if (gamePhase == 1)
			{
				serverPrint("Game number "+gameNumber+". Preparing table and players.");
				deck.shuffleDeck();
				if(gameNumber == 1)
				{
					dealer = rng.nextInt(allPlayers);
				}
				else
				{
					dealer = (dealer+1)%allPlayers;
				}
				informAll(allPlayers + "DBS__" + dealer + " " +(dealer+1)%allPlayers + " " + (dealer+2)%allPlayers);
				playersHands = new Hand[allPlayers];
				for(int j=0; j<playersHands.length; j++)
				{
						playersHands[j] = new Hand(deck);
				}
				synchronized(this)
				{
					for(int i=0; i<sharedWorker.length; i++)
					{
						if(sharedWorker[i] != null)
						{
							sharedWorker[i].sendToClient(allPlayers+"BEG__"+i+playersHands[i].toString(false));
							serverPrint("Players "+i+" hand looks like this:\n" + playersHands[i].toString(true));
						}
					}
				}
				gamePhase = 2;
				serverPrint("Preparations are done. Entering phase "+gamePhase+".");
			}
			if(gamePhase == 2)
			{
				while(gamePhase<9)
				{
					if(gamePhase == -1)
					{
						break;
					}
					else
					{
						if((gamePhase%2) == 0)
						{
							serverPrint("Betting phase.");
						}
						else
						{
							serverPrint("Card exchange phase.");
						}
						gamePhase = phaser(gamePhase);
						serverPrint("Phase "+(gamePhase-1)+" is done. Entering phase "+gamePhase+".");
					}
				}
			}
			if(gamePhase == 9)
			{
				winnerFinder();
				gamePhase = 10;
			}
			if(gamePhase == 10)
			{
				start = nextRonud();
				if(start)
				{
					for(int i=0; i<allPlayers; i++)
					{
						sharedWorker[i].nextMatch();
					}
					gameNumber++;
					deck = new Deck();
					for(int i=0; i<maximalBet.length; i++)
					{
						maximalBet[i] = 0;
					}
					gamePhase = 1;
				}
			}
			
		}
		serverPrint("It seems that someone escaped from the game. Waiting for another player...");
	}
}